use crate::recipe::RecipeReader;
use crate::recipe::ChunkPointer;
use std::fmt::Debug;
use std::fs::{File,create_dir_all};
use std::fs::OpenOptions;
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use parking_lot::Mutex;

use pipelines::Sender;

use super::RestoreError;
use super::RestoreResult;
use super::RestoreStatistics;

pub struct FileBuilder {
	reader_path:String,
	restore_path:String,
	statistics:Arc<Mutex<RestoreStatistics>>,
}

impl FileBuilder {
	pub fn from_path<P: AsRef<Path>+?Sized>(reader_path:&P,restore_path:&P,statistics:Arc<Mutex<RestoreStatistics>>)
									 -> RestoreResult<Self> {
		create_dir_all(restore_path)
			.map_err(|e|
				RestoreError::DirCreate(restore_path.as_ref().to_str().unwrap().to_string(), e)
			)?;
		Ok(Self{
			reader_path:reader_path.as_ref().to_str().unwrap().to_string(),
			restore_path:restore_path.as_ref().to_str().unwrap().to_string(),
			statistics:statistics,
		})
	}
	
	pub fn build_async<T:ChunkPointer+Send+Debug>(&self,out:&Sender<FileBuilderProduct<T>>) -> RestoreResult<()> {
		let mut cost_time = 0;
		let mut recipe_reader = RecipeReader::<T>::from_path(&self.reader_path)?;
		loop {
			let now = Instant::now();
			match recipe_reader.next_file()? {
				Some(file_info) => {
					//create file

					let path:&Path = file_info.name().as_ref();
					let restore_path:&Path = self.restore_path.as_ref();
					let create_path = if path.is_absolute() {
						path.to_path_buf()
					} else {
						restore_path.to_path_buf().join(path)
					};
					if let Some(parent_dir) = create_path.parent() {
						std::fs::create_dir_all(parent_dir)
							.map_err(|e|
								RestoreError::DirCreate(parent_dir.to_str().unwrap().to_string(), e)
							)?;
					}
					let err_create_path = create_path.clone();
					let file = OpenOptions::new().write(true).create(true).open(create_path)
										.map_err(|e|RestoreError::FileCreate(err_create_path.to_str().unwrap().to_string(), e))?;
					cost_time += now.elapsed().as_micros();
					out.send(FileBuilderProduct::File(file_info.name().clone(),file));
					
					//send chunk pointer
					let chunk_pointer_num = file_info.num();
					for _ in 0..chunk_pointer_num {
						out.send(FileBuilderProduct::Pointer(
							recipe_reader.next_chunk_pointer()?.unwrap()
						));
					}
				}
				None => {
					break;
				}
			}
		}
		self.statistics.lock().set_file_phase_time(cost_time);
		Ok(())
	}
}

pub enum FileBuilderProduct<T:ChunkPointer> {
	File(String,File),
	Pointer(Box<T>),
}
